💡지난 기술부채 포스팅에 있는 Test Double에 대한 정리 내용을 참고하면 도움이 될 듯 하다.
Mockito란?
: Mock 객체를 쉽게 만들고 관리하고 검증할 수 있는 방법을 제공해주는 프레임워크
-
Stub
: 인터페이스or기본 클래스가 최소한으로 구현되어 있는 상태의 객체(상태 기반 테스트)- 테스트 코드에서 Mock 객체를 사용할 때 Mock의 특정 메서드를 정의하는 행위를 Stubbing한다고 한다.
-
Spy
: 실제 객체처럼 동작하면서 객체의 메서드 호출 정보를 저장하거나 사용자가 정의한 로직을 수행한다.(행위 기반 테스트) -
Mock
: 사용자가 정의한 대로 동작하도록 프로그래밍된 객체(행위 기반 테스트)⚠️실제 객체에서 부분적으로만 Stub으로서 기능하는 Spy와 달리 Mock은 가짜 오브젝트이기 때문에 사용하고자 하는 메서드를 모두 직접 구현해주어야 한다.
Mockito를 사용하는 이유
- 애플리케이션에서 구현하지 않은 메서드를 테스트하려고 할 때, 해당 클래스가 어떻게 동작하는지 미리 생각하고 계산하며 테스트를 작성해야 한다면 매우 불편할 것이다.
- 이럴 때 미리 Mock 객체를 생성해서 사용하면 조금 더 편하게 테스트할 수 있다.
- 개발자가 컨트롤하기 힘든 부분이나 아직 구현되지 않은 클래스를 사용해야 할 때 사용하는 것이 좋다.
Mockito 의존성 추가
Maven Repository: org.mockito » mockito-core » 2.1.0
gradle 파일의 dependency 단락에 다음 라인을 추가해준다.
testImplementation 'org.mockito:mockito-junit-jupiter:5.2.0'
⚠️스프링 부트 2.2+ 버전으로 프로젝트를 생성할 경우 자동으로 Mockito 의존성이 추가된다.
Mockito 문법 사용하기
Mock 만들기
@Mock
어노테이션을 사용하는 방법@Mock ArrayList<String> mockedList;
- 직접 선언하는 방법
ArrayList<String> mockedList = mock(List.class);
Spy 만들기
@Spy
어노테이션을 사용하는 방법@Spy ArrayList<String> spyList;
- 직접 선언하는 방법
ArrayList<String> spyList = Mockito.spy(List.class);
@InjectMock
해당 객체의 멤버 변수로 존재하는 의존된 다른 객체들이 mock혹은 spy로 생성된 객체라면 의존성 주입을 해준다.
다음과 같이 Car 클래스 객체가 Name이라는 객체를 컴포지션 관계로 가지고 있다.
public class Car {
private final Name name;
public Car(Name name) {
this.name = name;
}
public boolean checkCar(String name) {
return this.name.isEqualsName(name);
}
}
public class Name {
private final String name;
public Name(String name) {
this.name = name;
}
public boolean isEqualsName(String name) {
return this.name.equals(name);
}
}
-
@InjectMocks
어노테이션을 사용하지 않는 경우-
Mock 객체를 직접 생성자에 넣어 Mock 객체가 주입된 Car 객체를 만들어야 한다.
@Mock private Name name; @DisplayName("InjectMocks를 사용하지 않고 Mock 의존성을 주입받는 방법") @Test void non_inject_mocks() { // given final Car car = new Car(name); // when Mockito.when(car.checkCar("hyundai")).thenReturn(true); // then Assertions.assertThat(car.checkCar("hyundai")).isTrue(); Assertions.assertThat(car.checkCar("kia")).isFalse(); }
-
-
@InjectMocks
어노테이션을 사용한 경우- Car를 생성할 때 의존성을 주입하지 않아도 Mock 또는 Spy로 생성된 객체를 주입해준다.
@Mock private Name name; @InjectMocks private Car car; @DisplayName("InjectMocks를 사용해서 Mock 의존성을 주입받는 방법") @Test void inject_mocks() { // when Mockito.when(car.checkCar("hyundai")).thenReturn(true); // then Assertions.assertThat(car.checkCar("hyundai")).isTrue(); Assertions.assertThat(car.checkCar("kia")).isFalse(); }
Verify
테스트하고자 하는 메서드가 의도한 대로 동작하는지 검증하는 것을 말한다. (행위 검증)
- 메서드가 호출된 횟수 검증
-
mockedList 객체에 대해 add(”a”)가 2번 호출되었는지 검증
verify(mockedList, times(2)).add("a");
-
mockedList 객체에 대해 add(1)이 한 번도 호출되지 않았는지 검증
verify(mockedList, never()).get(1);
-
mockedList 객체에 대해 add(”a”)가 최소 한 번은 호출되었는지 검증
verify(mockedList, atLeastOnce()).add("a");
-
mockedList 객체에 대해 add(”a”)가 최소 N 번은 호출되었는지 검증
verify(mockedList, atLeast(3)).add("a");
When-Then
mock 객체의 메서드를 원하는 방식으로 정의(stubbing)할 수 있다.
when(mock객체.메서드(파라미터)).thenReturn(반환값);
위와 같이 작성해주면, mock 객체
에 대해 파라미터
값을 넣고 메서드
를 호출해주면 반환값을 반환한다.
-
예제
listMock 객체에 대해
get(1)
또는get(2)
…등등 임의의 정수 값을 넣어 메서드를 호출하면 STUB_RETURN_VALUE로 선언한 값이 반환된다.@Test @DisplayName("반환값이 존재하는 메서드를 stubbing - when_thenReturn") void whenConfigNonVoidReturnMethodStub1() { List<Integer> listMock = mock(List.class); when(listMock.get(anyInt())).thenReturn(STUB_RETURN_VALUE); final Integer getValue = listMock.get(SECURE_RANDOM.nextInt()); assertThat(getValue) .isEqualTo(STUB_RETURN_VALUE); }